/*******************************************************************************
 * Descharts library
 * Copyright (c) 2014 Bradipao <bradipao@gmail.com>
 * https://plus.google.com/+SidBradipao
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package it.bradipao.lib.descharts;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.render.Texture;
import ohos.agp.utils.Color;
import ohos.agp.utils.TextAlignment;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;

import java.util.ArrayList;

/**
 * LineChartView() class represents a line graph widget.
 *
 * @since 2021-08-09
 */
public class LineChartView extends CartesianView {
    private ArrayList<ChartValueSerie> mSeries = new ArrayList<ChartValueSerie>();
    private int mXnum = 0;
    private int mLabelMaxNum = 10;
    private Paint mPnt = new Paint();

    /**
     * Constructor.
     *
     * @param context context
     */
    public LineChartView(Context context) {
        super(context);
        init();
    }

    /**
     * Constructor.
     *
     * @param context context
     * @param attrs attrs
     */
    public LineChartView(Context context, AttrSet attrs) {
        super(context, attrs);
        init();
    }

    @Override
    public void onDraw(Component component, Canvas cnv) {
        if ((mBmp == null) || isRedraw) {
            getViewSizes();
            getxyminmax();
            if (isYscaleAuto) {
                calcYgridRange();
            }
            calcxycoefs();
            PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
            options.pixelFormat = PixelFormat.ARGB_8888;
            options.size = new Size(mWidth, mHeight);
            mBmp = PixelMap.create(options);
            Texture texture = new Texture(mBmp);
            mCnv = new Canvas(texture);
            if (isGridVis) {
                drawGrid();
            }
            if (isXtextVis) {
                drawXlabel();
            }
            if (isYtextVis) {
                drawYlabel();
            }
            if (isBorderVis) {
                drawBorder();
            }
            if (isAxisVis) {
                drawAxis();
            }
            drawData();
            isRedraw = false;
        }
        cnv.drawPixelMapHolder(new PixelMapHolder(mBmp), 0, 0, mBmpPant);
    }

    /**
     * Clears ArrayList of series.
     */
    public void clearSeries() {
        while (mSeries.size() > 0) {
            mSeries.remove(0);
        }
        isRedraw = true;
        invalidate();
    }

    /**
     * Adds a serie to data holder ArrayList.
     *
     * @param serie serie
     */
    public void addSerie(ChartValueSerie serie) {
        mSeries.add(serie);
        isRedraw = true;
        invalidate();
    }

    /**
     * Returns ArrayList of series.
     *
     * @return ArrayList
     */
    public ArrayList<ChartValueSerie> getSeries() {
        return mSeries;
    }

    /**
     * Sets line visibility.
     *
     * @param index index
     * @param show show
     */
    public void setLineVis(int index, boolean show) {
        mSeries.get(index).setVisible(show);
        isRedraw = true;
        invalidate();
    }

    /**
     * Sets line color and size.
     *
     * @param index index
     * @param color color
     * @param size size
     */
    public void setLineStyle(int index, int color, float size) {
        mSeries.get(index).setStyle(color, size);
        isRedraw = true;
        invalidate();
    }

    /**
     * Sets line color and size and dip.
     *
     * @param index index
     * @param color color
     * @param size size
     * @param usedip usedip
     */
    public void setLineStyle(int index, int color, float size, boolean usedip) {
        mSeries.get(index).setStyle(color, size, usedip);
        isRedraw = true;
        invalidate();
    }

    /**
     * Sets maximum number of labels on X axis.
     *
     * @param maxnum maxnum
     */
    public void setLabelMaxNum(int maxnum) {
        if (maxnum <= 0) return;
        mLabelMaxNum = maxnum;
        isRedraw = true;
        invalidate();
    }

    /**
     * Gets X,Y ranges across all series
     */
    protected void getxyminmax() {
        ChartValueSerie serie;
        for (mI = 0; mI < mSeries.size(); mI++) {
            serie = mSeries.get(mI);
            if (mI == 0) {
                mXnum = serie.getSize();
                mYmin = serie.mYmin;
                mYmax = serie.mYmax;
            } else {
                if (serie.getSize() > mXnum) {
                    mXnum = serie.getSize();
                }
                if (serie.mYmin < mYmin) {
                    mYmin = serie.mYmin;
                }
                if (serie.mYmax > mYmax) {
                    mYmax = serie.mYmax;
                }
            }
        }
    }

    /**
     * Draw data from all series
     */
    protected void drawData() {
        float pY;
        boolean pValid;
        for (ChartValueSerie serie : mSeries) {
            if (serie.isVisible()) {
                // set paint
                mPnt.reset();
                mPnt.setStyle(Paint.Style.STROKE_STYLE);
                mPnt.setColor(new Color(serie.mColor));
                if (serie.mUseDip) {
                    mPnt.setStrokeWidth(dipToPixel(serie.mWidth));
                } else {
                    mPnt.setStrokeWidth(serie.mWidth);
                }
                mPnt.setAntiAlias(true);
                // iterate through points
                pValid = false;
                mPath.reset();
                for (mI = 0; mI < serie.mPointList.size(); mI++) {
                    pY = serie.mPointList.get(mI).y;
                    if (Float.isNaN(pY)) {
                        pValid = false;
                    } else if (!pValid) {
                        mPath.moveTo(mX + mBx + mI * mAx, mEy - (pY - mBy) * mAy);
                        pValid = true;
                    } else {
                        mPath.lineTo(mX + mBx + mI * mAx, mEy - (pY - mBy) * mAy);
                    }
                }
                mCnv.drawPath(mPath, mPnt);
            }
        }
    }

    /**
     * Calculates drawing coefficients
     */
    protected void calcxycoefs() {
        mAx = (float) mDx / mXnum;
        mBx = (float) mAx / 2;
        mAy = (float) mDy / Math.abs(mYmaxGrid - mYminGrid);
        mBy = (float) mYminGrid;
    }

    /**
     * Draw X label on top or bottom
     */
    protected void drawXlabel() {
        mPntText.setTextAlign(TextAlignment.CENTER);
        mPath.reset();
        ChartValueSerie mLabel = mSeries.get(0);
        String label;
        int numlab = mLabel.getSize();
        int numdiv = 1 + (numlab - 1) / mLabelMaxNum;
        if (isXtextBottom) {
            for (mI = 0; mI < mLabel.getSize(); mI++) {
                mPath.moveTo(mX + mBx + mI * mAx, mEy - 3);
                mPath.lineTo(mX + mBx + mI * mAx, mEy + 3);
                label = mLabel.mPointList.get(mI).t;
                if ((label != null) && (mI < numlab) && ((mI % numdiv) == 0)) {
                    mCnv.drawText(mPntText, label, mX + mBx + mI * mAx, mEy + mTextSize + 2);
                }
            }
        } else {
            for (mI = 0; mI < mLabel.getSize(); mI++) {
                mPath.moveTo(mX + mBx + mI * mAx, mY - 3);
                mPath.lineTo(mX + mBx + mI * mAx, mY + 3);
                label = mLabel.mPointList.get(mI).t;
                if ((label != null) && (mI < numlab) && ((mI % numdiv) == 0)) {
                    mCnv.drawText(mPntText, label, mX + mBx + mI * mAx, mY - 9);
                }
            }
        }
        mCnv.drawPath(mPath, mPntAxis);
    }
}
